/*
* 模块描述：这是一个可配置的多路组相联cache模块
* 组织方式：组相联 关联数：SET_ASSOC默认为4
* 容量：CACHE_SIZE
* 块大小：LINE_WIDTH
* 写策略：写返回、写分配 
* ram地址位宽：6bit, data表使用4个1KB的ram
* ram数据位宽：128bit
* 写粒度：支持字节
* 待处理： 1. uncache的情况
*         2. （byte size）单字节、半字、字、双字访问的情况 √
*         3. 可以用python针对不同的配置生成代码
* 注：1. verilog2001不支持在for里面定义循环变量i √已修改
*     2. 仅测试了DATA_WIDTH=32/64, SET_ASSOC=4的情况
*/

module cache_controller #(
    parameter CACHE_SIZE = 4 * 1024 * 8,
    parameter LINE_WIDTH = 128,
    parameter SET_ASSOC = 4,    //support SET_ASSOC = 2, 4, 8
    parameter DATA_WIDTH = 32, // CPU data bitwidth
    parameter ADDR_WIDTH = 32  // CPU address bitwidth
) (
    input clk,
    input rst,

    //CPU->cache
    input cpu_req_valid,
    input cpu_req_rw,
    input [ADDR_WIDTH-1:0] cpu_req_addr,
    input [DATA_WIDTH-1:0] cpu_req_wdata,
    input [DATA_WIDTH/8-1:0] cpu_req_wstrb,

    //cache->CPU
    output reg [DATA_WIDTH-1:0] cache_rsp_data,
    output reg cache_rsp_ready,

    //cache-> memory
    output reg cache_req_rw,
    output reg cache_req_valid,
    output reg [31:0] cache_req_addr,
    output reg [127:0] cache_req_data, //write data

    //memory->cache
    input [127:0] mem_rsp_data,
    input mem_rsp_ready

);

localparam INDEX_NUM = CACHE_SIZE / (LINE_WIDTH * SET_ASSOC);
localparam DATA_PER_LINE = LINE_WIDTH / DATA_WIDTH;

localparam DATA_BYTE_OFFSET = $clog2(DATA_WIDTH/8);
localparam LINE_BYTE_OFFSET = $clog2(LINE_WIDTH/8);
localparam INDEX_WIDTH = $clog2(INDEX_NUM);
localparam TAG_WIDTH = ADDR_WIDTH - INDEX_WIDTH - LINE_BYTE_OFFSET;
localparam WAY_NUM_WIDTH = $clog2(SET_ASSOC);
localparam DATA_WIDTH_LOG2 = $clog2(DATA_WIDTH);
localparam LINE_WIDTH_LOG2 = $clog2(LINE_WIDTH);

//signal connnet to tag ram and data ram
reg  [SET_ASSOC-1:0] tag_we;
wire [INDEX_WIDTH-1:0] cache_index;
reg [TAG_WIDTH+1:0] tag_wdata;
wire [TAG_WIDTH+1:0] tag_rdata[0:SET_ASSOC-1];

reg  [SET_ASSOC-1:0] data_we;
reg  [LINE_WIDTH-1:0] data_wdata;
wire [LINE_WIDTH-1:0] data_rdata[0:SET_ASSOC-1];

//connet to lru
reg cache_update_entry;
reg [WAY_NUM_WIDTH-1:0] cache_update_way;
wire [WAY_NUM_WIDTH-1:0] replace_way_id;


//--------------------------------------

wire [INDEX_WIDTH-1:0] cpu_req_index;
wire [TAG_WIDTH-1:0] cpu_req_tag;
wire [LINE_BYTE_OFFSET-DATA_BYTE_OFFSET-1:0] cpu_req_offset;
wire [DATA_WIDTH-1:0] cpu_req_wdata_msk;
wire [DATA_WIDTH-1:0] data_bit_en;
wire [LINE_WIDTH_LOG2-1:0] data_shift_param;
wire [LINE_WIDTH-1:0] data_write;           //cpu request wdata wrap 
wire [DATA_WIDTH-1:0] data_read ;           //data read to cpu
wire [LINE_WIDTH-1:0] data_read_temp ;
wire [TAG_WIDTH+1:0] cache_line_tag [0:SET_ASSOC-1];
wire [SET_ASSOC-1:0] cache_line_valid;
wire [SET_ASSOC-1:0] cache_line_dirty;

wire [SET_ASSOC-1:0] cache_way_hit;
reg  [WAY_NUM_WIDTH-1:0] cache_way_hit_id;
wire cache_hit;
wire cache_line_clean;

wire [LINE_WIDTH-1:0] hit_cache_line_data;
wire [TAG_WIDTH+1:0] hit_cache_line_tag;
wire [LINE_WIDTH-1:0] cache_data_msk;
 
wire [TAG_WIDTH+1:0] replace_line_tag;
wire [LINE_WIDTH-1:0] replace_line_data;

//cpu request addr process
assign cpu_req_index = cpu_req_addr[LINE_BYTE_OFFSET+INDEX_WIDTH-1:LINE_BYTE_OFFSET];
assign cpu_req_tag = cpu_req_addr[31: LINE_BYTE_OFFSET+INDEX_WIDTH];
assign cpu_req_offset = cpu_req_addr[LINE_BYTE_OFFSET-1:DATA_BYTE_OFFSET];

//data process
generate
    genvar i;
    for (i = 0;i < DATA_WIDTH/8; i=i+1 ) begin
        assign cpu_req_wdata_msk[i*8+7:i*8] = cpu_req_wdata[i*8+7:i*8] & {8{cpu_req_wstrb[i]}};
        assign data_bit_en[i*8+7:i*8] = {8{cpu_req_wstrb[i]}};
    end
endgenerate

assign data_shift_param = cpu_req_offset << DATA_WIDTH_LOG2;
assign cache_data_msk = ~(data_bit_en << data_shift_param); //fixme 
assign data_write = (cpu_req_wdata_msk << data_shift_param) | (hit_cache_line_data & cache_data_msk) ;    //将cpu的写请求数据放到cache line对应的位置  

assign replace_line_tag = tag_rdata[replace_way_id];    
assign replace_line_data = data_rdata[replace_way_id];


assign cache_hit = |cache_way_hit;
assign cache_line_clean = (~replace_line_tag[TAG_WIDTH+1]) | (~replace_line_tag[TAG_WIDTH]); //cache line is not dirty or valid --> cache line is clean 

generate
    genvar j;
    for(j=0;j < SET_ASSOC; j = j+1) begin
        assign cache_line_valid[j] = tag_rdata[j][TAG_WIDTH+1] ;
        assign cache_line_dirty[j] = tag_rdata[j][TAG_WIDTH] ;
        assign cache_line_tag[j] = tag_rdata[j][TAG_WIDTH-1:0];
        assign cache_way_hit[j]= (cpu_req_tag==cache_line_tag[j]) & cache_line_valid[j];
    end
endgenerate


//fsm
localparam IDLE=3'd0;
localparam COMPARE_TAG=3'd1;
localparam ALLOCATE=3'd2;
localparam WRITE_BACK=3'd3;

reg [2:0] current_state, next_state;
reg cache_rsp_ready_nxt;
reg [DATA_WIDTH-1:0] cache_rsp_data_nxt;
reg cache_req_rw_nxt;
reg cache_req_valid_nxt;
reg [31:0] cache_req_addr_nxt;
reg [127:0] cache_req_data_nxt;

always @(posedge clk) begin
    if(rst) begin
        current_state <= 'd0;
        cache_rsp_data <= 'd0;
        cache_rsp_ready <= 'd0;
        cache_req_rw <= 1'b0;
        cache_req_valid <= 1'b0;
        cache_req_addr <= 'd0;
        cache_req_data <= 'd0;
    end
    else begin
        current_state <= next_state;
        cache_rsp_data <= cache_rsp_data_nxt;
        cache_rsp_ready <= cache_rsp_ready_nxt;
        cache_req_rw <= cache_req_rw_nxt;
        cache_req_valid <= cache_req_valid_nxt;
        cache_req_addr <= cache_req_addr_nxt;
        cache_req_data <= cache_req_data_nxt; 
    end
end

always @(*) begin
    next_state = current_state;
    tag_we = {SET_ASSOC{1'b0}};         //always read tag and data ram by defult state
    data_we = {SET_ASSOC{1'b0}};
    tag_wdata = 'd0;
    data_wdata = 'd0;
    cache_rsp_ready_nxt = 1'b0;
    cache_rsp_data_nxt = data_read;
    cache_update_entry = 1'b0;
    cache_update_way = cache_way_hit_id;
    cache_req_rw_nxt = cache_req_rw;
    cache_req_valid_nxt = cache_req_valid;
    cache_req_addr_nxt = cache_req_addr;
    cache_req_data_nxt = cache_req_data;
    case (current_state)
        IDLE: begin
            if (cpu_req_valid) begin
                next_state = COMPARE_TAG;
            end
        end
        COMPARE_TAG : begin
            if(cache_hit) begin
                next_state = IDLE;
                cache_rsp_ready_nxt = 1'b1;
                cache_update_entry = 1'b1;
                cache_update_way = cache_way_hit_id;
                if(cpu_req_rw) begin  // write request
                    tag_we = cache_way_hit;
                    data_we = cache_way_hit;
                    tag_wdata = {1'b1,1'b1,hit_cache_line_tag[TAG_WIDTH-1:0]};
                    data_wdata = data_write;
                end
            end
            else if(cache_line_clean) begin //cache miss and cache line to replace is clean, read data from memory
                next_state = ALLOCATE;
                cache_req_rw_nxt = 1'b0;
                cache_req_valid_nxt = 1'b1;
                cache_req_addr_nxt = cpu_req_addr;
            end
            else begin                      //cache miss and cache line to replace is dirty, write back the cacheline to memory first
                next_state = WRITE_BACK;
                cache_req_rw_nxt = 1'b1;
                cache_req_valid_nxt = 1'b1;
                cache_req_addr_nxt = {replace_line_tag[TAG_WIDTH-1:0],cpu_req_addr[LINE_BYTE_OFFSET+INDEX_WIDTH-1:0]};
                cache_req_data_nxt = replace_line_data;
            end
        end
        ALLOCATE : begin
            if(mem_rsp_ready) begin         //refill cache line
                next_state = IDLE;
                tag_we[replace_way_id] = 1'b1;
                data_we[replace_way_id] = 1'b1;
                tag_wdata = {1'b1,1'b0,cpu_req_tag};
                data_wdata = mem_rsp_data;
                cache_req_valid_nxt = 1'b0;
            end
        end
        WRITE_BACK : begin
            if(mem_rsp_ready) begin
                next_state = ALLOCATE;
                cache_req_rw_nxt = 1'b0;
                cache_req_valid_nxt = 1'b1;
                cache_req_addr_nxt = cpu_req_addr;
            end
        end
    endcase
end

generate
    if(SET_ASSOC==2) begin
        always @(*) begin 
            cache_way_hit_id = 'd0;
            case (1'b1)
                cache_way_hit[0] : begin 
                    cache_way_hit_id = 'd0;
                end
                cache_way_hit[1] : begin 
                    cache_way_hit_id = 'd1;
                end
            endcase
        end
    end
    else if(SET_ASSOC==4) begin
        always @(*) begin 
            cache_way_hit_id = 'd0;
            case (1'b1)
                cache_way_hit[0] : begin 
                    cache_way_hit_id = 'd0;
                end
                cache_way_hit[1] : begin 
                    cache_way_hit_id = 'd1;
                end
                cache_way_hit[2] : begin 
                    cache_way_hit_id = 'd2;
                end
                cache_way_hit[3] : begin 
                    cache_way_hit_id = 'd3;
                end
            endcase
        end
    end
    else if(SET_ASSOC==8) begin
        always @(*) begin 
            cache_way_hit_id = 'd0;
            case (1'b1)
                cache_way_hit[0] : begin 
                    cache_way_hit_id = 'd0;
                end
                cache_way_hit[1] : begin 
                    cache_way_hit_id = 'd1;
                end
                cache_way_hit[2] : begin 
                    cache_way_hit_id = 'd2;
                end
                cache_way_hit[3] : begin 
                    cache_way_hit_id = 'd3;
                end
                cache_way_hit[4] : begin 
                    cache_way_hit_id = 'd4;
                end
                cache_way_hit[5] : begin 
                    cache_way_hit_id = 'd5;
                end
                cache_way_hit[6] : begin 
                    cache_way_hit_id = 'd6;
                end
                cache_way_hit[7] : begin 
                    cache_way_hit_id = 'd7;
                end                                               
            endcase
        end
    end
    else begin
        initial begin
            $display("SET_ASSOC parmater ERROR!");
        end
    end
endgenerate

assign data_read_temp = data_rdata[cache_way_hit_id];
assign hit_cache_line_data = data_rdata[cache_way_hit_id];
assign hit_cache_line_tag = tag_rdata[cache_way_hit_id];



//----------------------------------
generate
    genvar k;
    for(k=0;k < SET_ASSOC; k = k+1) begin: gen_cache_mem
        ram #(
            .Bits(TAG_WIDTH+2),          // | valid | dirty |     TAG     |
            .Word_Depth(INDEX_NUM),
            .Add_Width(INDEX_WIDTH)
        ) ctag_ram(
            .clk(clk),
            .cen(1'b0),
            .wen(~tag_we[k]),
            .a(cpu_req_index),
            .d(tag_wdata),
            .q(tag_rdata[k])
        );

        ram #(
            .Bits(LINE_WIDTH),
            .Word_Depth(INDEX_NUM),
            .Add_Width(INDEX_WIDTH)
        ) cdata_ram(
            .clk(clk),
            .cen(1'b0),
            .wen(~data_we[k]),
            .a(cpu_req_index),
            .d(data_wdata),
            .q(data_rdata[k])
        );

    end
endgenerate


        
data_sel #( .LINE_WIDTH(LINE_WIDTH),
            .DATA_WIDTH(DATA_WIDTH),
            .DATA_SEL_WIDTH(LINE_BYTE_OFFSET-DATA_BYTE_OFFSET)
) data_rdata_sel(
    .data_in(data_read_temp),
    .data_sel(cpu_req_offset),
    .data_out(data_read)
);

    
matrix_lru #(.SIZE(SET_ASSOC)
)matrix_lru0(
    .clk(clk),
    .rst(rst),
    .update_entry(cache_update_entry),
    .update_index(cache_update_way),
    .lru_index(replace_way_id)
);

endmodule

//----------------------------------
module data_sel #(                  //LINE_WIDTH->DATA_WIDTH
    parameter LINE_WIDTH = 128,
    parameter DATA_WIDTH = 32,
    parameter DATA_SEL_WIDTH = 2
) (
    input [LINE_WIDTH-1 : 0] data_in,
    input [DATA_SEL_WIDTH-1 :0] data_sel,
    output reg [DATA_WIDTH-1:0] data_out
);
localparam DATA_PER_LINE = LINE_WIDTH / DATA_WIDTH;
wire [DATA_PER_LINE-1:0] data_block_en;

generate
    if(DATA_PER_LINE==2) begin //2mux1
      always @(*) begin
          case (data_sel)
              1'b0 : data_out = data_in[DATA_WIDTH-1:0];
              1'b1 : data_out = data_in[DATA_WIDTH*2-1:DATA_WIDTH];
          endcase
      end
    end
    else if(DATA_PER_LINE==4) begin //4mux1
      always @(*) begin
          case (data_sel)
            2'b00 : data_out = data_in[DATA_WIDTH-1:0];
            2'b01 : data_out = data_in[DATA_WIDTH*2-1:DATA_WIDTH];
            2'b10 : data_out = data_in[DATA_WIDTH*3-1:DATA_WIDTH*2];
            2'b11 : data_out = data_in[DATA_WIDTH*4-1:DATA_WIDTH*3];
          endcase
      end
    end
    else if(DATA_PER_LINE==8) begin //8mux1
      always @(*) begin
          case (data_sel)
            3'b000 : data_out = data_in[DATA_WIDTH-1:0];
            3'b001 : data_out = data_in[DATA_WIDTH*2-1:DATA_WIDTH];
            3'b010 : data_out = data_in[DATA_WIDTH*3-1:DATA_WIDTH*2];
            3'b011 : data_out = data_in[DATA_WIDTH*4-1:DATA_WIDTH*3];
            3'b100 : data_out = data_in[DATA_WIDTH*5-1:DATA_WIDTH*4];
            3'b101 : data_out = data_in[DATA_WIDTH*6-1:DATA_WIDTH*5];
            3'b110 : data_out = data_in[DATA_WIDTH*7-1:DATA_WIDTH*6];
            3'b111 : data_out = data_in[DATA_WIDTH*8-1:DATA_WIDTH*7];
          endcase
      end
    end
endgenerate


    
endmodule